/**
 * @file worker.c
 * @brief Implementazione del thread worker del bibserver.
 * @author Marco Luisi
 * Si dichiara che il contenuto di questo file e' in ogni sua parte opera originale dell' autore.
 */

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "bib.h"
#include "biblio.h"
#include "comsock.h"
#include "worker.h"

/** @name Variabili esterne
 * Dichiarate e inizializzate dal server
 */
/**@{*/
extern FILE* logfile;
extern int records;
extern struct tm *loan_end;
extern const char* lookup_table[];
extern scheda_t **biblio;
extern pthread_mutex_t biblio_mtx;
extern pthread_mutex_t workers_mtx;
extern char log_loan[];
extern char log_query[];
/**@}*/

/** @name Variabili globali ma locali al thread
 * Thread local storage
 */
/**@{*/
/** File descriptor del socket a cui inviare le risposte*/
__thread int client_connection;
/** Signal set del thread*/
__thread sigset_t set;
/**@}*/

/**
 *
 * @param sfd
 */
void* worker_start(void *sfd) {
	/** Numero di libri che soddisfano le richieste */
	int books_num;
	/** Risultato del parsing dei campi nel messaggio di richiesta */
	int field_code;
	int i;
	/** Offset sulla stringa da parsare, generato da sscanf */
	int offset;
	/** Esito della funzione receive */
	int rcv;
	/** Puntatore che scorre la stringa del messaggio */
	char *ptr;
	/** Record da inviare al client */
	char *record;
	/**  Stringa per il file di log */
	char *logstring;
	/**  Stringa per il campo della richiesta */
	char field[FIELDMAX];
	/**  Stringa per il valore del campo della richiesta */
	char value[LLSTRING];
	/**  Array con le stringhe per la ricerca dei libri*/
	char *values[NCAMPI];
	/**  Messaggio di richiesta */
	message_t request;
	/**  Messaggio di risposta */
	message_t reply;
	/**  Array dei libri che soddisfano la richiesta */
	scheda_t **books;

	client_connection = *(int*) sfd;
	/*Gestione dei segnali*/
	if(worker_signal_handling() == ERROR){
		closeConnection(client_connection);
		pthread_exit(NULL);
	}

	/*Ricezione messaggio dal client*/
	rcv = receiveMessage(client_connection, &request);
	if (rcv == -1) {
		closeConnection(client_connection);
		pthread_exit(NULL );
	}
	/*Parsing della richiesta*/
	ptr = request.buffer;
	while (sscanf(ptr, "%19[^:]: %1023[^;]%n", field, value, &offset) == 2) {
		field_code = lookup(field);
		switch (field_code) {
		case (AUTORE): {
			values[AUTORE] = strdup(value);
			break;
		}
		case (TITOLO): {
			values[TITOLO] = strdup(value);
			break;
		}
		case (EDITORE): {
			values[EDITORE] = strdup(value);
			break;
		}
		case (LUOGO_PUBBLICAZIONE): {
			values[LUOGO_PUBBLICAZIONE] = strdup(value);
			break;
		}
		case (ANNO): {
			values[ANNO] = strdup(value);
			break;
		}
		case (COLLOCAZIONE): {
			values[COLLOCAZIONE] = strdup(value);
			break;
		}
		case (DESCRIZIONE_FISICA): {
			values[DESCRIZIONE_FISICA] = strdup(value);
			break;
		}
		case (NOTA): {
			values[NOTA] = strdup(value);
			break;
		}
		case (ERROR): {
			message_setup(&reply,REQ_ERROR,MSG_ERROR);
			sendMessage(client_connection, &reply);
			closeConnection(client_connection);
			pthread_exit(NULL);
			break;
		}
		}
		ptr += offset;
		if (*ptr != ';') {
			break;
		}
		++ptr; /*salta il delimitatore*/
	}
	/*Si prepara la stringa da scrivere sul file di log*/
	if(request.type == MSG_LOAN){
		logstring  = log_loan;
	}
	else {
		logstring = log_query;
	}
	/*Campi della richiesta configurati, si effettua la ricerca*/
	pthread_mutex_lock(&biblio_mtx);
	books_num = search(&books,values,request.type);
	pthread_mutex_unlock(&biblio_mtx);
	switch(books_num){
	case(0):{
		message_setup(&reply,NO_BOOKS,MSG_NO);
		sendMessage(client_connection, &reply);
		break;
	}
	case(-1):{
		message_setup(&reply,SEARCH_ERROR,MSG_ERROR);
		sendMessage(client_connection, &reply);
		break;
	}
	default:{
		for(i = 0; i < books_num; i++){
			record = scheda_to_record(books[i]);
			fprintf(logfile,"%s",record);
			fflush(logfile);
			message_setup(&reply,record,MSG_RECORD);
			sendMessage(client_connection, &reply);
			free(record);
		}
		break;
	}

	}

	free(books);
	closeConnection(client_connection);
	pthread_mutex_lock(&workers_mtx);
	fprintf(logfile,"%s %d\n",logstring,books_num);
	fflush(logfile);
	pthread_mutex_unlock(&workers_mtx);
	pthread_exit(NULL);
}

/**
 * @brief Impostazione della gestione dei segnali per il thread
 *
 * @return esito della funzione
 */
int worker_signal_handling(void) {
	struct sigaction *sa;
	sa = calloc(sizeof(struct sigaction), 1);
	/*Si mascherano tutti i segnali per il thread worker*/
	EC_M1(sigfillset(&set));
	EC_M1(pthread_sigmask(SIG_SETMASK, &set, NULL ));
	memset(sa, sizeof(sa), 0);
	sa->sa_handler = SIG_IGN;
	sigaction(SIGQUIT, sa, NULL );
	free(sa);
	return (OK);
}

/**
 * @brief Ricerca dei libri secondo i parametri ricevuti dal client
 *
 * La funzione scorre l'array delle schede bibliografiche, controllando, con la funzione book_match, ogni scheda se soddisfa le opzioni di ricerca.
 * Inizializza un array di puntatori a schede in base alla dimensione della biblioteca; se book_match trova un libro, viene copiato il puntatore alla scheda nell'array risultato.
 *
 *
 * @param books libri biblioteca
 * @param values array delle stringhe dei parametri di ricerca
 * @param type tipo della richiesta: prestito o ricerca
 * @return matches numero di libri trovati o codice di errore
 */
int search(scheda_t **books[],char **values, char type) {
	int matches = 0;
	/** Esito funzione book_match */
	int match;
	int dim;
	int i;
	/** Array dei puntatori alle struct risultato*/
	scheda_t **found_books;

	if(values == NULL){
		errno = EINVAL;
		return (ERROR);
	}

	dim = records /4;
	MALLOC_M1(found_books, dim * sizeof(scheda_t*));
	for (i = 0; i < records; i++) {
		/*Si scartano subito i libri non disponibili in caso di richiesta di prestito*/
		if ( (type == MSG_LOAN) & (biblio[i]->prestito.disponibile == FALSE) ) {
			continue;
		}
		match = book_match(values, biblio[i]);
		switch(match){
		case (FALSE):{
			continue;
		}
		case (ERROR):{
			free(found_books);
			found_books = NULL;
			return (ERROR);
			break;
		}
		default:{ /*Trovato un libro*/
			if (type == MSG_LOAN) { /*Si aggiornano i dati del prestito*/
				biblio[i]->prestito.disponibile = FALSE;
				biblio[i]->prestito.scadenza.tm_mday = loan_end->tm_mday;
				biblio[i]->prestito.scadenza.tm_mon = loan_end->tm_mon;
				biblio[i]->prestito.scadenza.tm_year = loan_end->tm_year;
			}
			found_books[matches] = biblio[i];
			matches++;
			if (matches == dim) { /*Se la lista dei libri è piena si ridimensiona dinamicamente*/
				dim = dim + 5;
				found_books = realloc(found_books, dim * sizeof(scheda_t*));
			}
			break;
		}
		}

	}
	found_books = realloc(found_books, matches * sizeof(scheda_t*));
	if(matches < 1){
		free(found_books);
		found_books = NULL;
	}
	*books = found_books;
	return(matches);
}

/**
 * @brief Controlla se un libro soddisfa la richiesta
 *
 * Cicla sull'array dei campi della ricerca; quando il puntatore corrispondente alla valore di dato campo è non null, controlla
 * la corrispondenza tra la richiesta e il dato della scheda bibliografica.
 * Non appena viene fallito uno dei confronti si restituisce l'esito negativo
 * @param values array con i parametri di ricerca
 * @param book libro da confrontare
 * @return esito del confronto tra il libro e i parametri di ricerca
 */
int book_match(char *values[], scheda_t *book) {
	int j;
	int year;
	autore_t *aut;
	int aut_match = FALSE;
	if (book == NULL || values == NULL ) {
		errno = EINVAL;
		return (ERROR);
	}

	for (j = 0; j < NCAMPI; j++) {
		if (values[j] == NULL ) {
			continue;
		}
		switch (j) {
		case (AUTORE): {
			aut = book->autore;
			while (aut) {
				if (strstr(aut->cognome, values[AUTORE]) != NULL ) {
					aut_match = TRUE;
					break;
				}
				if (strstr(aut->nome, values[AUTORE]) != NULL ) {
					aut_match = TRUE;
					break;
				}
				aut = aut->next;
			}
			if (aut_match == FALSE){
				return (FALSE);
			}
			break;
		}

		case (TITOLO): {
			if (strstr(book->titolo, values[TITOLO]) == NULL ) {
				return (FALSE);
			}
			break;
		}

		case (EDITORE): {
			if (strstr(book->pub.editore, values[EDITORE]) == NULL ) {
				return (FALSE);
			}
			break;
		}

		case (LUOGO_PUBBLICAZIONE): {
			if (strstr(book->pub.luogo, values[LUOGO_PUBBLICAZIONE]) == NULL ) {
				return (FALSE);
			}
			break;
		}

		case (ANNO): {
			year = atoi(values[ANNO]);
			if (year == 0) { /* Errore nella conversione */
				return (ERROR);
			}
			if (year != book->pub.anno) {
				return (FALSE);
			}
			break;
		}

		case (COLLOCAZIONE): {
			if (strstr(book->collocazione, values[COLLOCAZIONE]) == NULL ) {
				return (FALSE);
			}
			break;
		}

		case (DESCRIZIONE_FISICA): {
			if (strstr(book->descrizione_fisica,
					values[DESCRIZIONE_FISICA]) == NULL) {
				return (FALSE);
			}
			break;
		}

		case (NOTA): {
			if (strstr(book->nota, values[NOTA]) == NULL ) {
				return (FALSE);
			}
			break;
		}

		}
	}
	return (TRUE);
}

/**
 * @brief Funzione di riconoscimento dei campi per la ricerca.
 *
 * Utilizza una tabella di look-up con le stringhe dei possibili campi
 *
 * @param string parametro da controllare
 * @return codice del campo corrispondente o codice d'errore
 */
int lookup(char *string) {
	int i;
	for (i = 0; i < (NCAMPI - 1); i++) {
		if (strcmp(string, lookup_table[i]) == 0)
			return (i);
	}
	return (ERROR);
}
